#ifndef __RXMPE_MATCHER__
#define __RXMPE_MATCHER__

#include "../comm/xcrc32.h"
#include "../comm/byteregion.h"
#include "mpcc.h"
#include "mpsession.h"

namespace xm
{
	typedef const uint8_t *		cbptr;

	static inline uint8_t lch( uint8_t ch )
	{
		if( ch >= 'A' && ch <= 'Z' ) return ch - 'A' + 'a';
		return ch;
	}

	struct MatchBuffer
	{
		LPCVOID	BasePtr;
		size_t	ByteSize;
	};

	struct CandidatePointes
	{
		size_t *	Points;
		size_t		Count;
		size_t		Valid;
	};

	typedef std::vector<byte_region_t> BaVector;

	template < class T, size_t MAXNUM >
	struct ez_stack_t
	{
		T		_stack[MAXNUM];
		size_t	_count;
		ez_stack_t() : _count(0) {};
		bool_t push( T val )
		{
			if( _count >= MAXNUM ) return FALSE;
			_stack[_count++] = val;
			return TRUE;
		}
		bool_t pop( T & valref )
		{
			if( !_count ) return FALSE;
			-- _count;
			valref = _stack[_count];
			return TRUE;
		}
		bool_t empty()
		{
			return 0 == _count;
		}
		bool_t full()
		{
			return _count >= MAXNUM;
		}
		bool_t top( T & valref )
		{
			if( !_count ) return FALSE;
			valref = _stack[_count-1];
			return TRUE;
		}
	};

	typedef struct _op16_t { uint8_t _1; uint8_t _2; } op16_t ;


	struct matcher_t
	{
		enum
		{
			PM_CASE_SENS = 1,
			PM_MATCHING_CHAR = 2,
		};
		enum
		{
			MAX_PUSH = 16,
		};

		matcher_t*							_parent;
		byte_region_t						_pattern;
		byte_region_ex_t					_data;
		ez_stack_t<size_t,16>				_off_stack;
		refp<xman_session_t>				_context;

		inline matcher_t( xman_session_t* context_, byte_region_t& pattern_, byte_region_ex_t & data_, matcher_t * parent_ )
			: _parent( parent_ ), _data( data_ ), _pattern(pattern_), _context(context_)
		{
		}

		inline bool_t case_senstive()
		{
			return (_context->match_ctrl&matcher_t::PM_CASE_SENS);
		}

		inline bool_t do_match( byte_region_t & matched, bool_t is_root = FALSE )
		{
			matched._current = _data.cur_ptr();
			matched._last = _data.cur_ptr();

			if( is_root )
			{
				enter_char_match_mode();

				cbptr datp = _data.cur_ptr();
				cbptr ptnp = _pattern.cur_ptr();

				if( !_data.seekp(PREFIX_LENGTH) || !_pattern.seekp(PREFIX_LENGTH) )
					return FALSE;

				int diff = case_senstive() ? memcmp( ptnp, datp, PREFIX_LENGTH ) : memicmp( ptnp, datp, PREFIX_LENGTH );

				if( diff ) return FALSE;
			}

			uint8_t chdat = 0, chptn = 0;

			if( !_pattern.fetchv( chptn ) )
				return (is_root != FALSE);

			while( _data.peekv( chdat ) )
			{
				if( chptn == '\\' )
				{
					if( ! _pattern.fetchv( chptn ) )
						return FALSE;

					if( chptn == '\\' )
					{
						enter_char_match_mode();
						bool same = (case_senstive()) ? (chptn == chdat) : lch(chptn)==lch(chdat);
						if( !same ) return FALSE;
						if( !_data.seekp(1) ) return FALSE;
					}
					else
					{
						leave_char_match_mode();
						if( chptn == op_go_base )
						{
							if( !_data.set_offset(0) )
								return FALSE;
						}
						else if( chptn == op_go_ep )
						{
							iptr<IPeReader> wpe( _context->archive );
							if( !wpe )
							{
								if( !_data.set_offset(0) )
									return FALSE;
							}
							else
							{
								DWORD dwEP = 0;
								if( !wpe->EntryPointRva( &dwEP ) || !dwEP )
									return FALSE;
								if( !wpe->Rva2Raw( dwEP, 1, &dwEP ) )
									return FALSE;
								if( !_data.set_offset(dwEP) )
									return FALSE;
							}
						}
						else if( chptn == op_go_tail )
						{
							
						}
						else if( chptn == op_jmp )
						{
							int32_t idist = 0;
							if( !_pattern.fetchlebs32( idist ) )
								return FALSE;
							if(! eval_seek_n( idist ) )
								return FALSE;
						}
						else if( chptn == op_jmpm )
						{
							int32_t idist = 0;
							if( !_pattern.fetchv( idist ) )
								return FALSE;
							if(! eval_seek_most( idist ) )
								return FALSE;
						}
						//else if( chptn == kInSet8 || chptn == kOutSet8 )
						//{
						//	if( !DoInCharSet( chptn == kInSet8, TRUE ) )
						//		return FALSE;
						//}
						//else if( chptn == kInSetM8 || chptn == kOutSetM8 )
						//{
						//	if( !DoInCharSet( chptn == kInSetM8, FALSE ) )
						//		return FALSE;
						//}
						else if( chptn == op_either2 )
						{
							uint8_t ptnlen1 = 0, ptnlen2 = 0;
							if( !_pattern.fetchv( ptnlen1 ) || !_pattern.fetchv( ptnlen2 ) )
								return FALSE;

							if( !eval_either_2( ptnlen1, ptnlen2 ) )
								return FALSE;
						}
						else if( chptn == op_eithern )
						{
							uint8_t nsubptn = 0;
							if( ! _pattern.fetchv( nsubptn ) )
								return FALSE;

							uint8_t * lenap = 0;
							if( ! _pattern.fetchp( lenap, nsubptn ) )
								return FALSE;

							typedef UTIL::sentry<byte_region_t*> n_byte_region_t;

							n_byte_region_t subptns = new byte_region_t[nsubptn];
							if( !subptns ) return FALSE;

							for( size_t i = 0; i < nsubptn; ++ i )
							{
								if( !_pattern.fetch_byte_region( subptns[i], lenap[i] ) )
									return FALSE;
							}
							if( !eval_either_n( subptns, nsubptn ) )
								return FALSE;
						}
						else if( chptn == op_getjmp )
						{
							uint8_t dbc = 0;
							if( ! _pattern.fetchv( dbc ) )
								return FALSE;
							if( dbc == 1 )
							{
								if( !tget_jump<int8_t>(0) )
									return FALSE;
							}
							else if( dbc == 2 )
							{
								if( !tget_jump<le::I16>(0) )
									return FALSE;
							}
							else 
							{
								if( !tget_jump<le::I32>(0) )
									return FALSE;
							}
						}
						else if( chptn == op_x86jmp || chptn == op_x86jmp_opt )
						{
							//
							// min length is 2 bytes
							//

							op16_t opc16 = {};
							if( !_data.peekv<op16_t>(opc16) )
								return FALSE;

							bool_t is_opt = (chptn == op_x86jmp_opt);
							if( 0xE8 == chdat || 0xE9 == chdat )			// jmp/call far
							{
								if( !tget_jump<le::I32>(1) )
									return FALSE;
							}
							else if( 0xEB == chdat )						// jmp 8
							{
								if( !tget_jump<int8_t>(1) )
									return FALSE;
							}
							//else if( (chdat & 0x7F) == chdat )		changed by Bin.Wu 2013.3.29
							else if ( (chdat & 0x70) == 0x70)         // jxx 8
							{
								if( !tget_jump<int8_t>(1) )
									return FALSE;
							}
							//else if( (chdat & 0xE3) == chdat )	  changed by Bin.Wu 2013.3.28
							else if ((chdat & 0xE0) == 0xE0 && (chdat & 0xC/*1100*/) == 0x0)    // loopne, loopne, loop, jecxz
							{
								// e0, e1, e2, e3
								if( tget_jump<int8_t>(1) )
									return FALSE;
							}
							else if( (0x0F == chdat) && ( (opc16._2 & 0x8F) == opc16._2 ) )						// jxx 32
							{
								if( !tget_jump<le::I32>(2) )
									return FALSE;
							}
							else
							{
								if( !is_opt	)
									return FALSE;
							}
						}
						else if( chptn == op_not )
						{
							le::U16 sublen;
							if( !_pattern.fetchv( sublen ) ) return FALSE;
							if( !eval_not(sublen) ) return FALSE;
						}
						else if( chptn == op_is_iat )
						{
							iptr<IKPETools> tools( _context->arch_tools );
							if( !tools )
								return FALSE;

							le::U32 uCRC;
							if( !_pattern.fetchv(uCRC) ) return FALSE;
							
							le::U32 uIAT;
							if( !_data.fetchv(uIAT) ) return FALSE;

							IKPETools::IMPITEM * item = tools->ImpFindByIAT(uIAT);
							if( !item ) return FALSE;

							if( uCRC != item->crc )
								return FALSE;
						}
						else if( chptn == op_repeat )
						{
							le::U16 sublen, min_times, max_times;
							if( !_pattern.fetchv( sublen ) || 
								!_pattern.fetchv( min_times ) ||
								!_pattern.fetchv( max_times ) )
								return FALSE;
							if( !eval_repeat( sublen, min_times, max_times ) )
								return FALSE;
						}
						else if( chptn == op_pushofs )
						{
							if( !_off_stack.push( _data.offset() ) )
								return FALSE;
						}
						else if( chptn == op_popofs )
						{
							size_t offset = 0;
							if( !_off_stack.pop(offset) ) 
								return FALSE;
							if( !_data.set_offset(offset) ) 
								return FALSE;
						}
						else if( chptn == op_topofs )
						{
							size_t nOffset = 0;
							if( !_off_stack.top(nOffset) )
								return FALSE;
							if( !_data.set_offset(nOffset) )
								return FALSE;
						}
						else if( chptn == op_imatch)
						{
							_context->match_ctrl &= (~PM_CASE_SENS);
						}
						else if( chptn == op_bmatch )
						{
							_context->match_ctrl |= PM_CASE_SENS;
						}
						//else if( chptn == kCRC32 )
						//{
						//	le::U32 uCRCWant;
						//	le::U32 uLength;
						//	PCHAR pbData = NULL;
						//	if( !_pattern.fetchv(uCRCWant)|| !_pattern.fetchv(uLength) || !_data.peekp( pbData, uLength ) )
						//		return FALSE;
						//	UINT32 uCrc32 = std_crc32( pbData, uLength );
						//	if( uCrc32 != uCRCWant )
						//		return FALSE;
						//	_data.seekp( uLength );
						//}
						//else if( chptn == kCRC32U )
						//{
						//	le::U32 uCRCWant;
						//	le::U32 uLength;
						//	PCHAR pbData = NULL;
						//	if( !_pattern.fetchv(uCRCWant)|| !_pattern.fetchv(uLength) || !_data.peekp( pbData, uLength ) )
						//		return FALSE;
						//	UINT32 uCrc32 = mpu_crc32( pbData, uLength );
						//	if( uCrc32 != uCRCWant )
						//		return FALSE;
						//	_data.seekp( uLength );
						//}
						else
						{
							// Unknown ControlCode
							return FALSE;
						}
					}
				}
				else
				{
					enter_char_match_mode();
					bool same = (case_senstive()) ? (chptn == chdat) : lch(chptn)==lch(chdat);
					if( !same ) return FALSE;
					if( !_data.seekp(1) ) return FALSE;
				}

				if( !_pattern.fetchv( chptn ) )
				{
					matched.set_last( _data.cur_ptr() );
					return TRUE;
				}
			}

			return FALSE;

		}
	protected:

		template < class T>
		inline bool_t tget_jump( LONG bSkip )
		{
			if( !_data.seekp(bSkip) )
				return FALSE;
			T ofs;
			if( !_data.fetchv(ofs) )
				return FALSE;
			cbptr lpLeft = _data.cur_ptr();
			if( !_data.seekp( ofs ) )
				return FALSE;
			return TRUE;
		}

		inline VOID enter_char_match_mode()
		{
			_context->match_ctrl |= PM_MATCHING_CHAR;
		}
		inline VOID leave_char_match_mode()
		{
			_context->match_ctrl &= ~PM_MATCHING_CHAR;
		}
		//////////////////////////////////////////////////////////////////////////
		//bool _do_chset_n( size_t rep,
		//inline bool_t DoInCharSet( bool_t bReqInSet, bool_t bExact )
		//{
		//	uint8_t bTimes, bSetDescLen;
		//	if( !_pattern.fetchv( bTimes ) || !_pattern.fetchv( bSetDescLen ) )
		//	{
		//		if( !bTimes ) return TRUE;
		//		if( !bSetDescLen ) return FALSE;
		//	}

		//	byte_region_t baSetDesc;
		//	if( !_pattern.fetch_byte_region( baSetDesc, bSetDescLen ) )
		//		return FALSE;

		//	CharSet chset;
		//	if( !chset.Parse( baSetDesc ) )
		//		return FALSE;

		//	cbptr pb = (cbptr)_data.cur_ptr();
		//	size_t matchCount = 0;
		//	for( matchCount; matchCount < bTimes; ++ matchCount )
		//	{
		//		uint8_t ch = 0;
		//		if( ! _data.peekv( ch ) ) break;
		//		bool_t bInSet = chset.IsInSet( ch );
		//		if( bInSet != bReqInSet ) break;
		//		_data.seekp(1);
		//	}

		//	if( matchCount < 1 ) return FALSE;

		//	if( bExact && matchCount != bTimes )
		//		return FALSE;

		//	return TRUE;
		//}
		//////////////////////////////////////////////////////////////////////////

		inline bool_t eval_seek_n( int32_t steps )
		{
			if( steps == 0 ) return TRUE;
			return _data.seekp( steps ) != 0;
		}

		inline bool_t eval_either_2( size_t ptnlen1, size_t ptnlen2 )
		{
			byte_region_t subptn[2];
			if( !_pattern.fetch_byte_region( subptn[0], ptnlen1 ) ||
				!_pattern.fetch_byte_region( subptn[1], ptnlen2 ) )
				return FALSE;
			return eval_either_n( subptn, 2 );
		}

		inline bool_t eval_seek_most( int32_t most_dist )
		{
			if( most_dist >= 0 )
			{
				for( int32_t i = 0; i <= most_dist; ++ i )
				{
					byte_region_ex_t subdat = _data;
					if( i ) if( !subdat.seekp( i ) ) break;

					matcher_t sub_matcher( _context, _pattern, subdat, this );

					byte_region_t matched;
					if( sub_matcher.do_match( matched ) )
					{
						return _data.set_current( matched.cur_ptr() );
					}
				}
			}
			else
			{
				for( int32_t i = 0; i >= most_dist ; -- i )
				{
					byte_region_ex_t subdat = _data;
					if( i ) if( !subdat.seekp( i ) ) break;

					matcher_t sub_matcher( _context, _pattern, subdat, this );

					byte_region_t matched;
					if( sub_matcher.do_match( matched ) )
					{
						return _data.set_current( matched.cur_ptr() );
					}
				}
			}
			return FALSE;
		}

		inline bool_t eval_either_n( byte_region_t * subptns, size_t nptn )
		{
			for( size_t i = 0; i < nptn; ++ i )
			{
				matcher_t sub_matcher( _context, subptns[i], _data, this );

				byte_region_t matched;
				
				if( sub_matcher.do_match( matched ) )
				{
					_data = sub_matcher._data;
					return TRUE;
				}
			}
			return FALSE;
		}
		inline bool_t eval_not( size_t sublen )
		{
			byte_region_t subptn;
			if( !_pattern.fetch_byte_region( subptn, sublen ) )
				return FALSE;

			matcher_t sub_matcher( _context, subptn, _data, this );

			byte_region_t matched;
			if( sub_matcher.do_match( matched ) )
				return FALSE;

			return TRUE;
		}
		inline bool_t eval_repeat( size_t sublen, size_t min_times, size_t max_times )
		{
			if( !sublen || max_times < min_times ) 
				return FALSE;

			byte_region_t subptn;
			if( !_pattern.fetch_byte_region( subptn, sublen ) )
				return FALSE;
			
			size_t i = 0;
			for( i = 0; i < min_times; ++ i )
			{
				matcher_t sub_matcher( _context, subptn, _data, this );

				byte_region_t matched;
				if( !sub_matcher.do_match( matched ) ) 
					return FALSE;
				
				_data = sub_matcher._data;
			}
			for( ; i < max_times; ++ i )
			{
				matcher_t sub_matcher( _context, subptn, _data, this );
				byte_region_t matched;
				if( !sub_matcher.do_match( matched ) )
					break;
				_data = sub_matcher._data;
			}
			// try MAX
			if( i == max_times )
			{
				matcher_t sub_matcher( _context, subptn, _data, this );
				byte_region_t matched;
				if( sub_matcher.do_match( matched ) )
					return FALSE;
			}
			return TRUE;
		}
	};

};


#endif
